// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: jhkim

#pragma once

#include <set>
#include <algorithm>
#include "coin2/strategy/order_executor/executor.h"
#include "coin2/strategy/linear_model/calc_util.h"
#include "presto/quant/base/time.h"

namespace coin2::strategy::linear_model {

using coin::proto::OrderDirection;
using coin::proto::OrderDuration;
using coin::proto::OrderType;


inline void ManageMmOrderLayering(
    ::coin2::strategy::order_executor::OrderExecutorSystem* oe,
    const coin2::exchange::base::symbology::IProductHolder& pholder,
    const Bbo& bbo,
    int64_t timestamp,

    BlockOrderMeasures* bom,
    double leverage_rate,

    double signal_skewed_lb,
    double signal_skewed_ub,
    double signal_sigma,

    bool buy_block,
    bool sell_block,

    const LmFocusConfig& fconf,
    
    double rate_used_ratio=0) {
  if (bbo.exchange_time > 0 && bbo.fetched_time > bbo.exchange_time + 500e6) {
    oe->CancelAllOrders(pholder, OrderDirection::DIRECTION_SELL, timestamp);
    oe->CancelAllOrders(pholder, OrderDirection::DIRECTION_BUY, timestamp);
    return;
  }

  double max_lot_size = oe->GetConfig(pholder).qty().max_lot_size();
  double ask0p = bbo.askp;
  double bid0p = bbo.bidp;
  double midp = 0.5 * (ask0p + bid0p);

  double threshold_multiplier = 1;
  if (fconf.enable_api_rate_limit_check() &&
      rate_used_ratio > fconf.reduce_order_above_api_rate() &&
      fconf.reduce_order_multiplier() > 0) {
    threshold_multiplier = fconf.reduce_order_multiplier() / (1 - rate_used_ratio + 0.01);
  }

  buy_block |= (signal_skewed_lb <= fconf.buy_threshold_bps() * threshold_multiplier);
  sell_block |= (signal_skewed_ub >= -fconf.buy_threshold_bps() * threshold_multiplier);
  if (fconf.has_buy_threshold_nsigma()) {
    buy_block |= (signal_skewed_lb <= (fconf.buy_threshold_nsigma() * signal_sigma) * threshold_multiplier);
    sell_block |= (signal_skewed_ub >= (-fconf.buy_threshold_nsigma() * signal_sigma) * threshold_multiplier);
  }

  // focus3: only supports simple execution
  CHECK(!fconf.use_minmax_block());

  auto& tickprc = pholder.product_info().tick_price();

  bool allow_buy_fill_cooltime =
    bom->prev_bid_fill_time + fconf.fill_cooltime_sec() * Time::kNanosecondsPerSecond < timestamp;
  bool allow_sell_fill_cooltime =
      bom->prev_ask_fill_time + fconf.fill_cooltime_sec() * Time::kNanosecondsPerSecond < timestamp;
  bool allow_buy_taker_cooltime =
      bom->prev_buy_taker_time + fconf.taker_cooltime_sec() * Time::kNanosecondsPerSecond < timestamp;
  bool allow_sell_taker_cooltime =
      bom->prev_sell_taker_time + fconf.taker_cooltime_sec() * Time::kNanosecondsPerSecond < timestamp;
  bool allow_buy_post_cooltime =
      bom->prev_bid_post_time + fconf.post_cooltime_sec() * Time::kNanosecondsPerSecond < timestamp;
  bool allow_sell_post_cooltime =
      bom->prev_ask_post_time + fconf.post_cooltime_sec() * Time::kNanosecondsPerSecond < timestamp;
  bool allow_taker_initiate_time =
      bom->strat_init_time + fconf.taker_warmup_sec() * Time::kNanosecondsPerSecond < timestamp;

  bool allow_buy_taker = allow_buy_fill_cooltime && allow_buy_taker_cooltime && allow_taker_initiate_time;
  bool allow_sell_taker = allow_sell_fill_cooltime && allow_sell_taker_cooltime && allow_taker_initiate_time;

  // threshold_bps cant be zero. will be waste of money
  if (fconf.has_threshold_bps() && fconf.threshold_bps() > 0) {
    double spdbps = 1e4 * (ask0p - bid0p) / midp;
    const double agg_threshold_bps = (fconf.threshold_bps() + fconf.threshold_bps_add()) * threshold_multiplier;
    const double hurdle_bps = agg_threshold_bps + 0.5 * spdbps;
    if (!buy_block && signal_skewed_lb > hurdle_bps && allow_buy_taker) {
      double extra_signal = signal_skewed_lb - hurdle_bps;
      double buy_aggp = ask0p * (1 + extra_signal * 1e-4);
      bom->prev_buy_taker_time = timestamp;
      oe->SubmitOrder(
          pholder,
          tickprc.RoundPassively(buy_aggp, OrderDirection::DIRECTION_BUY),
          max_lot_size,
          OrderDirection::DIRECTION_BUY,
          OrderDuration::GTC_ORDER,
          OrderType::LIMIT_ORDER,
          ask0p,
          leverage_rate,
          false,  // post_only
          "taker",
          timestamp,
          false,
          false);
      oe->CancelAllOrders(pholder, OrderDirection::DIRECTION_SELL, timestamp);
      oe->CancelAllOrders(pholder, OrderDirection::DIRECTION_BUY, timestamp);
      return;
    } else if (!sell_block && -signal_skewed_ub > hurdle_bps && allow_sell_taker) {
      double extra_signal = -signal_skewed_ub - hurdle_bps;
      double sell_aggp = bid0p * (1 - extra_signal * 1e-4);
      bom->prev_sell_taker_time = timestamp;
      oe->SubmitOrder(
          pholder,
          tickprc.RoundPassively(sell_aggp, OrderDirection::DIRECTION_SELL),
          max_lot_size,
          OrderDirection::DIRECTION_SELL,
          OrderDuration::GTC_ORDER,
          OrderType::LIMIT_ORDER,
          bid0p,
          leverage_rate,
          false,  // post_only
          "taker",
          timestamp,
          false,
          false);
      oe->CancelAllOrders(pholder, OrderDirection::DIRECTION_SELL, timestamp);
      oe->CancelAllOrders(pholder, OrderDirection::DIRECTION_BUY, timestamp);
      return;
    }
  }

  std::optional<double> bid_post_price;
  if (!buy_block) {
    bid_post_price = std::min(ask0p * (1 - fconf.min_bbo_dist_bps() * 1e-4), bid0p);
  }

  int num_layering = fconf.layering();
  CHECK_GT(num_layering, 1);
  if (bid_post_price && max_lot_size > 0 && allow_buy_post_cooltime) {
    bom->prev_bid_post_time = timestamp;
    oe->GetMutableExecutorSub(pholder)->ManageSideOrders(
        pholder,
        timestamp,
        1,
        leverage_rate,
        order_executor::LayeringLevels::EqualSize(
            &pholder.product_info(),
            num_layering,
            1,
            *bid_post_price / (1 + 50 * 1e-4),
            *bid_post_price,
            max_lot_size
            ));
  }

  std::optional<double> ask_post_price;
  if (!sell_block) {
    ask_post_price = std::max(bid0p * (1 + fconf.min_bbo_dist_bps() * 1e-4), ask0p);
  }

  if (ask_post_price && max_lot_size > 0 && allow_sell_post_cooltime) {
    bom->prev_ask_post_time = timestamp;
    oe->GetMutableExecutorSub(pholder)->ManageSideOrders(
        pholder,
        timestamp,
        -1,
        leverage_rate,
        order_executor::LayeringLevels::EqualSize(
            &pholder.product_info(),
            num_layering,
            -1,
            *ask_post_price,
            *ask_post_price * (1 + 50 * 1e-4),
            max_lot_size
            ));
  }
}

}  // namespace coin2::strategy::linear_model
